/*
 * Copyright (c) 2016.
 * Modified on 28/04/2016.
 */

package cm.aptoide.pt.utils;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import dalvik.system.DexFile;
import java.io.File;
import java.io.IOException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;

/**
 * Created by xudshen@hotmail.com on 14/11/13.
 *
 * @author xudshen@hotmail.com
 * @author SithEngineer
 */
public final class MultiDexHelper {

  public static final String EXTRACTED_SUFFIX = ".zip";
  private static final String EXTRACTED_NAME_EXT = ".classes";
  private static final String SECONDARY_FOLDER_NAME =
      "code_cache" + File.separator + "secondary-dexes";

  private static final String PREFS_FILE = "multidex.version";
  private static final String KEY_DEX_NUMBER = "dex.number";

  /**
   * get all the classes name in "classes.dex", "classes2.dex", ....
   *
   * @param context the application context
   *
   * @return all the classes name
   *
   * @throws PackageManager.NameNotFoundException
   * @throws IOException
   */
  public static List<Map.Entry<String, String>> getAllClasses(Context context)
      throws PackageManager.NameNotFoundException, IOException {
    List<Map.Entry<String, String>> classNames = new ArrayList<>();
    for (String path : getSourcePaths(context)) {
      DexFile dexfile = null;
      try {
        if (path.endsWith(EXTRACTED_SUFFIX)) {
          //NOT use new DexFile(path), because it will throw "permission error in
          // /data/dalvik-cache"
          dexfile = DexFile.loadDex(path, path + ".tmp", 0);
        } else {
          dexfile = new DexFile(path);
        }
        Enumeration<String> dexEntries = dexfile.entries();
        while (dexEntries.hasMoreElements()) {
          classNames.add(new AbstractMap.SimpleImmutableEntry<>(dexEntries.nextElement(), path));
        }
      } catch (IOException e) {
        throw new IOException("Error at loading dex file '" + path + "'");
      } finally {
        if (dexfile != null) {
          dexfile.close();
        }
      }
    }
    return classNames;
  }

  /**
   * get all the dex path
   *
   * @param context the application context
   *
   * @return all the dex path
   *
   * @throws PackageManager.NameNotFoundException
   * @throws IOException
   */
  public static List<String> getSourcePaths(Context context)
      throws PackageManager.NameNotFoundException, IOException {
    ApplicationInfo applicationInfo = context.getPackageManager()
        .getApplicationInfo(context.getPackageName(), 0);
    File sourceApk = new File(applicationInfo.sourceDir);
    File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);

    List<String> sourcePaths = new ArrayList<String>();
    sourcePaths.add(applicationInfo.sourceDir); //add the default apk path

    //the prefix of extracted file, ie: test.classes
    String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
    //the total dex numbers
    int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);

    for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
      //for each dex file, ie: test.classes2.zip, test.classes3.zip...
      String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
      File extractedFile = new File(dexDir, fileName);
      if (extractedFile.isFile()) {
        sourcePaths.add(extractedFile.getAbsolutePath());
        //we ignore the verify zip part
      } else {
        throw new IOException(
            "Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
      }
    }

    return sourcePaths;
  }

  private static SharedPreferences getMultiDexPreferences(Context context) {
    return context.getSharedPreferences(PREFS_FILE,
        Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB ? Context.MODE_PRIVATE
            : Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
  }
}
